package com.jzo2o.market.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.common.model.PageResult;
import com.jzo2o.common.utils.*;
import com.jzo2o.market.enums.ActivityStatusEnum;
import com.jzo2o.market.mapper.ActivityMapper;
import com.jzo2o.market.model.domain.Activity;
import com.jzo2o.market.model.dto.request.ActivityQueryForPageReqDTO;
import com.jzo2o.market.model.dto.request.ActivitySaveReqDTO;
import com.jzo2o.market.model.dto.response.ActivityInfoResDTO;
import com.jzo2o.market.model.dto.response.SeizeCouponInfoResDTO;
import com.jzo2o.market.service.IActivityService;
import com.jzo2o.market.service.ICouponService;
import com.jzo2o.market.service.ICouponWriteOffService;
import com.jzo2o.mysql.utils.PageUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.jzo2o.market.constants.RedisConstants.RedisKey.ACTIVITY_CACHE_LIST;
import static com.jzo2o.market.constants.RedisConstants.RedisKey.COUPON_RESOURCE_STOCK;
import static com.jzo2o.market.enums.ActivityStatusEnum.*;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author itcast
 * @since 2023-09-16
 */
@Service
public class ActivityServiceImpl extends ServiceImpl<ActivityMapper, Activity> implements IActivityService {
    private static final int MILLION = 1000000;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private ICouponService couponService;

    @Resource
    private ICouponWriteOffService couponWriteOffService;

    /**
     * 用户端抢券列表分页查询活动信息
     *
     * @param tabType 页面类型
     * @return
     */
    @Override
    public List<SeizeCouponInfoResDTO> queryForListFromCache(Integer tabType) {
        // 从缓存中查询活动优惠卷信息
        String json = redisTemplate.opsForValue().get(ACTIVITY_CACHE_LIST).toString();
        // 将Json序列化成java集合对象
        List<SeizeCouponInfoResDTO> dtoList = JsonUtils.toList(json, SeizeCouponInfoResDTO.class);
        // 将请求参数进行修改，保持和数据库的数据匹配
        int status = tabType == 1 ? DISTRIBUTING.getStatus() : NO_DISTRIBUTE.getStatus();
        // 根据修改的状态和优惠劵信息进行匹配过滤出不符合条件的数据
        List<SeizeCouponInfoResDTO> collect = dtoList.stream().filter(
                item -> status == realTimeStatus(
                        item.getDistributeStartTime(),
                        item.getDistributeEndTime(),
                        item.getStatus()
                )).peek(item -> {
                    item.setRemainNum(item.getStockNum());
                    item.setStatus(status);
                }
        ).collect(Collectors.toList());
        return collect;
    }

    /**
     * 状态实时同步
     * @param distributeStartTime 活动秒杀开始时间
     * @param distributeEndTime 活动秒杀结束时间
     * @param status 活动状态
     * @return 活动实时状态
     */
    private int realTimeStatus(LocalDateTime distributeStartTime, LocalDateTime distributeEndTime, Integer status) {
        LocalDateTime now = LocalDateTime.now();

        if(status == NO_DISTRIBUTE.getStatus()
            && distributeStartTime.isBefore(now)
            && distributeEndTime.isAfter(now)) {// 待生效，就判断当前时间是否是在秒杀开始时间和秒杀结束时间之间(进行中)
            return DISTRIBUTING.getStatus();
        } else if(status == NO_DISTRIBUTE.getStatus()
                    && distributeEndTime.isBefore(now)) {// 待生效，就判断当前时间是否是在秒杀结束时间之后(已结束)
            return LOSE_EFFICACY.getStatus();
        } else if(status == DISTRIBUTING.getStatus()
                    && distributeEndTime.isBefore(now)) {// 进行中，就判断当前时间是否是在秒杀结束时间之后(已结束)
            return LOSE_EFFICACY.getStatus();
        }
        return status;
    }

    @Override
    public PageResult<ActivityInfoResDTO> queryForPage(ActivityQueryForPageReqDTO activityQueryForPageReqDTO) {
        LocalDateTime now = DateUtils.now();
        // 1.查询准备
        LambdaQueryWrapper<Activity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 查询条件
        lambdaQueryWrapper.eq(ObjectUtils.isNotNull(activityQueryForPageReqDTO.getId()), Activity::getId, activityQueryForPageReqDTO.getId())
                .like(StringUtils.isNotEmpty(activityQueryForPageReqDTO.getName()), Activity::getName, activityQueryForPageReqDTO.getName())
                .eq(ObjectUtils.isNotNull(activityQueryForPageReqDTO.getType()), Activity::getType, activityQueryForPageReqDTO.getType())
                .eq(ObjectUtils.isNotNull(activityQueryForPageReqDTO.getStatus()), Activity::getStatus, activityQueryForPageReqDTO.getStatus());

        // 排序
        lambdaQueryWrapper.orderByDesc(Activity::getId);
        // 分页
        Page<Activity> activityPage = new Page<>(activityQueryForPageReqDTO.getPageNo().intValue(), activityQueryForPageReqDTO.getPageSize().intValue());
        activityPage = baseMapper.selectPage(activityPage, lambdaQueryWrapper);
        return PageUtils.toPage(activityPage, ActivityInfoResDTO.class);
    }

    @Override
    public ActivityInfoResDTO queryById(Long id) {
        // 1.获取活动
        Activity activity = baseMapper.selectById(id);
        // 判空
        if (activity == null) {
            return new ActivityInfoResDTO();
        }
        // 2.数据转换，并返回信息
        ActivityInfoResDTO activityInfoResDTO = BeanUtils.toBean(activity, ActivityInfoResDTO.class);
        // 设置状态
//        activityInfoResDTO.setStatus(getStatus(activity.getDistributeStartTime(), activity.getDistributeEndTime(), activity.getStatus()));
        // 3.领取数量
//        Integer receiveNum = couponService.countReceiveNumByActivityId(activity.getId());
        Integer receiveNum = activity.getTotalNum()-activity.getStockNum();
        activityInfoResDTO.setReceiveNum(receiveNum);
        // 4.核销量
        Integer writeOffNum = couponWriteOffService.countByActivityId(id);
        activityInfoResDTO.setWriteOffNum(NumberUtils.null2Zero(writeOffNum));

        //
        return activityInfoResDTO;
    }

    @Override
    public void save(ActivitySaveReqDTO activitySaveReqDTO) {
        // 1.逻辑校验
        activitySaveReqDTO.check();
        // 2.活动数据组装
        // 转换
        Activity activity = BeanUtils.toBean(activitySaveReqDTO, Activity.class);
        // 状态
        activity.setStatus(NO_DISTRIBUTE.getStatus());
        //库存
        activity.setStockNum(activitySaveReqDTO.getTotalNum());
        if(activitySaveReqDTO.getId() == null) {
            activity.setId(IdUtils.getSnowflakeNextId());
        }
        //排序字段
//        long sortBy = DateUtils.toEpochMilli(activity.getDistributeStartTime()) * MILLION + activity.getId() % MILLION;
        // 3.保存
        saveOrUpdate(activity);
    }


    @Override
    public void updateStatus() {
        LocalDateTime now = DateUtils.now();
        // 1.更新已经进行中的状态
        LambdaUpdateWrapper<Activity> updateWrapper1 = new LambdaUpdateWrapper<Activity>()
                .eq(Activity::getStatus, NO_DISTRIBUTE.getStatus())//检索待生效的活动
                .set(Activity::getStatus, DISTRIBUTING.getStatus())//更新活动状态为进行中
                .le(Activity::getDistributeStartTime, now)//活动开始时间小于等于当前时间
                .gt(Activity::getDistributeEndTime, now);//活动结束时间大于当前时间
        baseMapper.update(null, updateWrapper1);

        // 2.更新已经结束的
        LambdaUpdateWrapper<Activity> updateWrapper2 = new LambdaUpdateWrapper<Activity>()
                .set(Activity::getStatus, LOSE_EFFICACY.getStatus())//更新活动状态为已失效
                .in(Activity::getStatus, Arrays.asList(DISTRIBUTING.getStatus(), NO_DISTRIBUTE.getStatus()))//检索待生效及进行中的活动
                .lt(Activity::getDistributeEndTime, now);//活动结束时间小于当前时间
        baseMapper.update(null, updateWrapper2);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void revoke(Long id) {
        // 1.活动作废
        boolean update = lambdaUpdate()
                .set(Activity::getStatus, ActivityStatusEnum.VOIDED.getStatus())
                .eq(Activity::getId, id)
                .in(Activity::getStatus, Arrays.asList(NO_DISTRIBUTE.getStatus(), DISTRIBUTING.getStatus()))
                .update();
        if(!update) {
            return;
        }
        // 2.未使用优惠券作废
        couponService.revoke(id);

    }

    /**
     * 活动预热
     */
    @Override
    public void preHeat() {
        LocalDateTime now = LocalDateTime.now();
        // 查询疯抢中和即将开始的优惠卷信息
        List<Activity> activityList = lambdaQuery()
                .le(Activity::getDistributeStartTime, now.plusDays(30))
                .in(Activity::getStatus, List.of(1, 2))
                .orderByAsc(Activity::getCreateTime)
                .list();
        if(CollUtils.isEmpty(activityList)) {
            return;
        }
        // 将优惠卷信息进行数据转换存入缓存
        List<SeizeCouponInfoResDTO> dto =
                BeanUtils.copyToList(activityList, SeizeCouponInfoResDTO.class);
        // 将活动信息写入缓存
        redisTemplate.opsForValue().set(ACTIVITY_CACHE_LIST, JsonUtils.toJsonStr(dto));

        // 将活动库存写入缓存
        // 待生效状态的活动库存直接写入缓存
        dto.stream().filter(
                item -> NO_DISTRIBUTE.getStatus()
                        == realTimeStatus(item.getDistributeStartTime(), item.getDistributeEndTime(), item.getStatus())
        ).forEach(item -> {
            String key = String.format(COUPON_RESOURCE_STOCK, item.getId() % 10);
            redisTemplate.opsForHash().put(key, item.getId().toString(), item.getTotalNum());
        });
        // 进行中状态的活动库存，写入前需判断缓存中是否已经设置
        dto.stream().filter(
                item -> DISTRIBUTING.getStatus()
                        == realTimeStatus(item.getDistributeStartTime(), item.getDistributeEndTime(), item.getStatus())
        ).forEach(item -> {
            String key = String.format(COUPON_RESOURCE_STOCK, item.getId() % 10);
            redisTemplate.opsForHash().putIfAbsent(key, item.getId().toString(), item.getTotalNum());
        });
    }

    /**
     * 从缓存中获取活动信息
     * @param id
     * @return
     */
    @Override
    public ActivityInfoResDTO getActivityInfoByIdFromCache(Long id) {
        // 从缓存查询活动信息
        String json = redisTemplate.opsForValue().get(ACTIVITY_CACHE_LIST).toString();
        if(StringUtils.isEmpty(json)) {
            return null;
        }
        // 将活动信息序列化成java对象
        List<SeizeCouponInfoResDTO> dto = JsonUtils.toList(json, SeizeCouponInfoResDTO.class);
        // 对数据进行筛选

        return dto.stream()
                .filter(item -> Objects.equals(item.getId(), id))
                .map(item -> BeanUtils.toBean(item, ActivityInfoResDTO.class))
                .findFirst()
                .orElse(null);
    }

}
